Skip to content

build(nix): declare build inputs explicitly and fix dev shell env - #1781

Open
mmclinton wants to merge 4 commits into
vicinaehq:mainfrom
mmclinton:nix-dep-hygiene
Open

build(nix): declare build inputs explicitly and fix dev shell env#1781
mmclinton wants to merge 4 commits into
vicinaehq:mainfrom
mmclinton:nix-dep-hygiene

Conversation

@mmclinton

Copy link
Copy Markdown
Contributor

Summary

I've been building Vicinae from the flake and working in nix develop, and went looking for why a couple of things behaved oddly in the dev shell. That turned into four small commits: two on the package, two on the dev shell. The only change to the built output is the removed (inert) modules-load.d file; every other installed byte is identical. The evidence is in the collapsed section at the bottom so you can reproduce it rather than take my word for it.

Each commit stands alone, so happy to split this, drop any part, or reshape it entirely.

Commit File What
0ab07cfe nix/vicinae.nix Declare eight dependencies the build uses but never listed
5943d423 nix/vicinae.nix Stop installing lib/modules-load.d/vicinae.conf, which nothing reads from a store path
441b65a4 flake.nix Remove four shellHook exports that are duplicates or no-ops
85521f69 flake.nix Point the QML import paths at the qtEnv the shell already builds

1. Undeclared dependencies (0ab07cfe)

nix/vicinae.nix sets strictDeps = true, so I expected buildInputs to be the complete picture. The CMake actually requires eight things that aren't in it: wayland-scanner and wayland-protocols (cmake/Wayland.cmake), OpenSSL (src/lib/crypto, vendor/sqlcipher), X11::xcb / X11::xcb_keysyms (src/server), and the bare xkbcommon / udev link names (src/server, src/lib/linux-utils, src/snippet).

They resolve today because qt6.qtbase and layer-shell-qt propagate all eight transitively.

Nothing is broken. The reason to declare them anyway is that the arrangement is invisible exactly when it matters. Two cases from this repo, and neither is a criticism, they're the point:

If a nixpkgs bump ever changes what Qt propagates, configure fails with wayland-protocols not found in a diff that has nothing to do with Wayland, and whoever bisects it has a bad afternoon. I read this as finishing what strictDeps already opted into, but if you'd rather deliberately lean on Qt's propagation and keep the list minimal, that's a fair call and I'll drop this commit.

2. modules-load.d (5943d423)

INSTALL_MODULES_LOAD_CONFIG defaults to ON, so the package installs $out/lib/modules-load.d/vicinae.conf. systemd only reads modules-load.d from /etc, /run and /usr(/local)/lib, and NixOS generates /etc/modules-load.d from boot.kernelModules, so in a store path the file is inert, and it reads as though the package handles loading uinput when it doesn't. Same shape of problem as #1607 (a package manager wanting to own installed files, there for Gentoo); the two compose rather than conflict. That PR adds a coarse switch, and the Nix build wants the existing fine-grained one, since it does want the themes, desktop file and icon.

3. Dev shell (441b65a4, 85521f69)

Both are artifacts of ordering rather than anything anyone got wrong.

441b65a4: #1129 added export CC=${pkgs.gcc15}/bin/gcc (and CXX) to get gcc 15 into the shell, which was the right fix at the time. #1545 later rebuilt the shell as mkShell.override {stdenv = package.stdenv;}, and since gcc15Stdenv.cc is pkgs.gcc15 (verified below), the exports became a second copy of what the stdenv already does. The two CMAKE_*_COMPILER exports were no-ops all along: CMake reads CC/CXX from the environment and never the CMAKE_*_COMPILER variables (verified below, and documented). With the compiler exports gone the hook only carries the QML import paths, so this commit also drops the isLinux guard on the shellHook: qmlls is equally useful on macOS. Say the word if you'd rather keep it guarded.

85521f69: #1189 introduced the aggregate qtEnv; #1636 later added the QML import paths so qmlls could see Nix-installed QML modules, but pointed them at pkgs.qt6.qtdeclarative rather than the qtEnv that already existed, so qmlls saw a subset of what the shell provides (org.kde.layershell and QtWayland were missing). This is really just finishing #1636.

Verification: how I checked nothing changed (x86_64-linux, flake's pinned nixpkgs)

Nothing new enters the build. The .drv closures of main and this branch differ only in vicinae's own derivation, its source tree, and the two fixed-output npm-deps derivations (whose output hashes are pinned, so their outputs are identical):

# bash, from the branch checkout root, clean tree
diff <(nix-store -qR "$(nix eval --raw "git+file://$PWD?ref=main#packages.x86_64-linux.default.drvPath")" | sort) \
     <(nix-store -qR "$(nix eval --raw .#packages.x86_64-linux.default.drvPath)" | sort)
# -> 8 paths: one vicinae-0.24.0.drv and one *-source per side, two npm-deps.drv per side

All eight packages were already in main's build closure at identical versions: systemd-minimal-libs-259 (nixpkgs' udev), wayland-protocols-1.47, libxkbcommon-1.11.0, wayland-scanner-1.24.0, libxcb-1.17.0, libxcb-keysyms-0.4.1, libx11-1.8.12, openssl-3.6.1. All but wayland-protocols are in qt6.qtbase's propagatedBuildInputs; wayland-protocols is in layer-shell-qt's.

The installed tree is byte-identical. I built both derivations and compared the outputs after replacing each output's self-referencing store hash with a fixed placeholder (the store paths necessarily differ, and the binaries embed their own $out). The only difference left is the modules-load.d removal:

main=$(nix build "git+file://$PWD?ref=main" --no-link --print-out-paths)  # full source build
branch=$(nix build . --no-link --print-out-paths)
tmp=$(mktemp -d)
cp -r "$main" "$tmp/main" && cp -r "$branch" "$tmp/branch" && chmod -R u+w "$tmp"
export LC_ALL=C  # sed must treat the ELF binaries as bytes, not UTF-8
find "$tmp/main"   -type f -exec sed -i "s/$(basename "$main"   | head -c32)/@self@/g" {} +
find "$tmp/branch" -type f -exec sed -i "s/$(basename "$branch" | head -c32)/@self@/g" {} +
diff -r "$tmp/main" "$tmp/branch"
# -> Only in .../main/lib: modules-load.d   (the intended removal; nothing else)

The runtime closure holds one version of each library. openssl-3.6.1-bin/-dev do appear, but nix why-depends shows they arrive through nodejs (the bin/vicinae wrapper's PATH -> nodejs -> openssl-dev), which predates this change.

Package: nix build -L .#default green; lib/modules-load.d gone; all six installed programs present (bin/vicinae plus five under libexec/vicinae/). The built launcher runs (vicinae server --open on Wayland/GNOME: window opens, app search and launch work).

Dev shell:

  • nix develop -c sh -c '$CC --version' -> gcc (GCC) 15.2.0, unchanged.

  • gcc15Stdenv.cc == pkgs.gcc15 -> true (nix eval against the pinned nixpkgs).

  • The CMake claim, tested empirically in the shell: CMAKE_CXX_COMPILER=/nonexistent cmake ... configures fine (env var ignored); CXX=/nonexistent cmake ... fails with CMAKE_CXX_COMPILER not set (env var read).

  • $QML_IMPORT_PATH now resolves org/kde/layershell/{qmldir,LayerShellQtQml.qmltypes}, and the delta against plain qtdeclarative is exactly the two missing trees:

    $ comm -13 <(ls $qtdeclarative/lib/qt-6/qml) <(ls $qtEnv/lib/qt-6/qml)
    org          # org.kde.layershell
    QtWayland

Hygiene: alejandra --check . clean. Eval is warning-free: the commit uses the top-level libx11 / libxcb / libxcb-keysyms attributes because the pinned nixpkgs deprecates the xorg.* set ("The xorg package set has been deprecated", pkgs/top-level/aliases.nix). The package and dev shell also evaluate cleanly at each of the four commits, and still evaluate for aarch64-darwin.

Not verified: macOS beyond evaluation. The Linux-only additions sit inside the existing lib.optionals isLinux blocks, so the Darwin path should be untouched, but I'd appreciate a check from someone who can build it.

Happy to clarify any of these further.

mmc added 4 commits August 10, 2026 10:15
The build asks for these by name, but none of them were listed: CMake
resolves wayland-scanner with find_program(... REQUIRED), reads
wayland-protocols' pkgdatadir through pkg-config, requires OpenSSL for
the crypto lib and the vendored sqlcipher, requires X11 for the xcb
window manager, and links xkbcommon and udev by bare name.

They resolved anyway, because qt6.qtbase propagates openssl, libx11,
libxcb, libxcb-keysyms, libxkbcommon, systemd and wayland-scanner, and
kdePackages.layer-shell-qt propagates wayland-protocols. Nothing
declares that arrangement and nothing tests it.

d344b47 is why it matters. Unvendoring the merged protocols made system
wayland-protocols a hard configure-time requirement, FATAL_ERROR and
all. nix/vicinae.nix was not touched, and the Nix build stayed green
only because layer-shell-qt happened to carry it. Whenever that path is
disturbed, by a buildInput dropped or a nixpkgs bump that changes what
Qt propagates, configure fails with "wayland-protocols not found" in a
diff that has nothing to do with Wayland.

The derivation already sets strictDeps, so this finishes a rigour it had
opted into. Every package added here was already in the build graph at
the same version, so the closure is unchanged.
systemd only reads modules-load.d from /etc, /run and /usr/lib, and
NixOS generates /etc/modules-load.d from boot.kernelModules. The
vicinae.conf we install into the store path is therefore inert, and it
suggests the package takes care of loading uinput when it does not.

Loading uinput on NixOS belongs in the NixOS module, not here.
CMAKE_C_COMPILER and CMAKE_CXX_COMPILER are not environment variables.
CMake reads CC and CXX and stores their values into the CMAKE_*_COMPILER
cache entries; unlike CMAKE_TOOLCHAIN_FILE it never looks the compiler
variables up in the environment, so both exports were no-ops.

CC and CXX were redundant too: the shell is already built with
mkShell.override { stdenv = package.stdenv; }, package.stdenv is
gcc15Stdenv, and gcc15Stdenv.cc is pkgs.gcc15, the same wrapper the
stdenv puts on PATH and exports itself. Pinning them by hand also
quietly defeated that override, since the shell would have kept using
gcc 15 if effectiveStdenv ever moved on.

With the compiler exports gone, the hook only carries the QML import
paths, and those are just as useful on macOS, so the isLinux guard on
the shellHook goes too.

Ref: https://cmake.org/cmake/help/latest/envvar/CC.html
The dev shell builds a qtEnv aggregating qtdeclarative, qtsvg,
qtimageformats, qttools and, on Linux, qtwayland and layer-shell-qt,
but pointed the QML import paths at qtdeclarative alone. Comparing the
two trees, that hid exactly the modules the aggregate exists for:
org.kde.layershell and QtWayland.
@mmclinton mmclinton changed the title Nix dep hygiene nix dependency :q Aug 10, 2026
@mmclinton mmclinton changed the title nix dependency :q nix dependency hygiene Aug 10, 2026
@mmclinton mmclinton changed the title nix dependency hygiene build(nix): declare build inputs explicitly and fix dev shell env Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant